#include "swpprocesseditor.h"

#include "../documents/data/graphicdata.h"
#include "../undo/processeditorcommands.h"
#include "../utils/undomanager.h"
#include "items/graphicitemcommon.h"
#include "items/swpactivityconnectorgraphicitem.h"
#include "items/swpactivitygraphicitem.h"
#include "items/swpdecisiongraphicitem.h"
#include "items/swpendpointgraphicitem.h"
#include "items/swpjoingraphicitem.h"
#include "items/swpmergegraphicitem.h"
#include "items/swpstartpointgraphicitem.h"
#include "items/swpsplitgraphicitem.h"
#include "items/swpswimlanegraphicitem.h"

#include <gallerycommon.h>
#include <gdmodelquerymaker.h>
#include <iconmanager.h>

#include <QtCore/QMimeData>
#include <QtCore/QStringList>
#include <QtGui/QAction>
#include <QtGui/QDragEnterEvent>
#include <QtGui/QDragMoveEvent>
#include <QtGui/QDropEvent>
#include <QtGui/QGraphicsView>
#include <QtGui/QToolBar>

using namespace core;
using namespace gui;
using namespace swp;

const QString SwpProcessEditor::ACCEPTED_MIMETYPE =
    "application/x-swp-itemgraphicitem";
const QString SwpProcessEditor::START_POINT_MIMETYPE_DATA =
    "START_POINT_MIMETYPE_DATA";
const QString SwpProcessEditor::END_POINT_MIMETYPE_DATA =
    "END_POINT_MIMETYPE_DATA";
const QString SwpProcessEditor::DECISION_MIMETYPE_DATA =
    "DECISION_MIMETYPE_DATA";
const QString SwpProcessEditor::ACTIVITY_MIMETYPE_DATA =
    "ACTIVITY_MIMETYPE_DATA";
const QString SwpProcessEditor::JOIN_MIMETYPE_DATA =
    "JOIN_MIMETYPE_DATA";
const QString SwpProcessEditor::SPLIT_MIMETYPE_DATA =
    "SPLIT_MIMETYPE_DATA";
const QString SwpProcessEditor::MERGE_MIMETYPE_DATA =
    "MERGE_MIMETYPE_DATA";

SwpProcessEditor::SwpProcessEditor(QWidget *parent) :
    AbstractGraphicEditor(parent),
    m_addStartPointAction(0),
    m_addEndPointAction(0),
    m_addDecisionAction(0),
    m_addActivityAction(0),
    m_addJoinAction(0),
    m_addSplitAction(0),
    m_addMergeAction(0),
    m_createConnectorAction(0),
    m_addPoolLaneAction(0),
    m_startPointMimeData(0),
    m_endPointMimeData(0),
    m_decisionMimeData(0),
    m_activityMimeData(0),
    m_joinMimeData(0),
    m_splitMimeData(0),
    m_mergeMimeData(0),
    m_startPointThumbnailDrag(),
    m_startPointThumbnailGallery(),
    m_endPointThumbnailDrag(),
    m_endPointThumbnailGallery(),
    m_decisionThumbnailDrag(),
    m_decisionThumbnailGallery(),
    m_activityThumbnailDrag(),
    m_activityThumbnailGallery(),
    m_joinThumbnailDrag(),
    m_joinThumbnailGallery(),
    m_splitThumbnailDrag(),
    m_splitThumbnailGallery(),
    m_mergeThumbnailDrag(),
    m_mergeThumbnailGallery(),
    m_connectorThumbnailGallery(),
    m_connectorThumbnailConnect()
{
    createThumbnails();
    createMimeData();
    createActions();
    connectSlots();

    m_view->viewport()->installEventFilter(this);
    m_view->setAcceptDrops(true);

    IconManager* iconManager = IconManager::getInstance();
    iconManager->registerStyleIcon("SwpAddSwimlane",
        ":/swp/icons/add_swimlane_icon.png");
}

SwpProcessEditor::~SwpProcessEditor()
{
    delete m_startPointMimeData;
    delete m_endPointMimeData;
    delete m_decisionMimeData;
    delete m_activityMimeData;
}

QStringList SwpProcessEditor::galleryGroups() const
{
    QStringList ret;

    ret << tr("Items");
    ret << tr("Transitions");

    return ret;
}

QList<GalleryItem> SwpProcessEditor::galleryItems(const QString& group) const
{
    QList<GalleryItem> ret;

    if (group == tr("Items"))
    {
        GalleryItem spGalItem;
        spGalItem.Thumbnail = GalleryItemThumbnail(
            m_startPointThumbnailGallery, m_startPointThumbnailDrag);
        spGalItem.Label = tr("Start point");
        spGalItem.Action = m_addStartPointAction;
        spGalItem.DragData = m_startPointMimeData;

        GalleryItem epGalItem;
        epGalItem.Thumbnail = GalleryItemThumbnail(
            m_endPointThumbnailGallery, m_endPointThumbnailDrag);
        epGalItem.Label = tr("End point");
        epGalItem.Action = m_addEndPointAction;
        epGalItem.DragData = m_endPointMimeData;

        GalleryItem deGalItem;
        deGalItem.Thumbnail = GalleryItemThumbnail(
            m_decisionThumbnailGallery, m_decisionThumbnailDrag);
        deGalItem.Label = tr("Decision");
        deGalItem.Action = m_addDecisionAction;
        deGalItem.DragData = m_decisionMimeData;

        GalleryItem acGalItem;
        acGalItem.Thumbnail = GalleryItemThumbnail(
            m_activityThumbnailGallery, m_activityThumbnailDrag);
        acGalItem.Label = tr("Activity");
        acGalItem.Action = m_addActivityAction;
        acGalItem.DragData = m_activityMimeData;

        GalleryItem joGalItem;
        joGalItem.Thumbnail = GalleryItemThumbnail(
            m_joinThumbnailGallery, m_joinThumbnailDrag);
        joGalItem.Label = tr("Join");
        joGalItem.Action = m_addJoinAction;
        joGalItem.DragData = m_joinMimeData;

        GalleryItem slGalItem;
        slGalItem.Thumbnail = GalleryItemThumbnail(
            m_splitThumbnailGallery, m_splitThumbnailDrag);
        slGalItem.Label = tr("Split");
        slGalItem.Action = m_addSplitAction;
        slGalItem.DragData = m_splitMimeData;

        GalleryItem meGalItem;
        meGalItem.Thumbnail = GalleryItemThumbnail(
            m_mergeThumbnailGallery, m_mergeThumbnailDrag);
        meGalItem.Label = tr("Merge");
        meGalItem.Action = m_addMergeAction;
        meGalItem.DragData = m_mergeMimeData;

        ret.append(acGalItem);
        ret.append(deGalItem);
        ret.append(joGalItem);
        ret.append(slGalItem);
        ret.append(spGalItem);
        ret.append(meGalItem);
        ret.append(epGalItem);
    }
    else if (group == tr("Transitions"))
    {
        GalleryItem cnGalItem;
        cnGalItem.Thumbnail = GalleryItemThumbnail(
            m_connectorThumbnailGallery, QPixmap(),
            m_connectorThumbnailConnect);
        cnGalItem.Label = tr("Connection");
        cnGalItem.Action = m_createConnectorAction;
        cnGalItem.ConnectData = new ConnectionData(CONNECTION_ACTIVITY);

        ret.append(cnGalItem);
    }
    else
    {
        // do nothing
    }

    return ret;
}

IEditor* SwpProcessEditor::copy() const
{
    return new SwpProcessEditor();
}

bool SwpProcessEditor::eventFilter(QObject* watched, QEvent* event)
{
    bool ret = false;

    if (watched == m_view->viewport())
    {
        if (event->type() == QEvent::DragEnter)
        {
            dragEnterEvent(dynamic_cast<QDragEnterEvent* >(event));
            ret = true;
        }
        else if (event->type() == QEvent::DragMove)
        {
            dragMoveEvent(dynamic_cast<QDragMoveEvent* >(event));
            ret = true;
        }
        else if (event->type() == QEvent::Drop)
        {
            dropEvent(dynamic_cast<QDropEvent* >(event));
            ret = true;
        }
        else
        {
            ret = AbstractGraphicEditor::eventFilter(watched, event);
        }
    }
    else
    {
        ret = AbstractGraphicEditor::eventFilter(watched, event);
    }

    return ret;
}

RemoveGraphicItemCommand* SwpProcessEditor::removeItemCommand(
    core::IGraphicEditorItem *item) const
{
    RemoveGraphicItemCommand* ret = 0;

    if (item->type() == SWP_SWIMLANE_ITEM)
    {
        SwpSwimlaneGraphicItem* _item = dynamic_cast<SwpSwimlaneGraphicItem*>(
            item);

        Q_ASSERT(_item != 0); // item posing as SwpSwimlaneGraphicItem

        ret = new RemoveSwimlaneCommand(m_documentData, _item);
    }
    else
    {
        ret = new RemoveGraphicItemCommand(m_documentData, item);
    }

    return ret;
}

void SwpProcessEditor::dragEnterEvent(QDragEnterEvent* event)
{
    if (event->mimeData()->hasFormat(ACCEPTED_MIMETYPE))
    {
        event->acceptProposedAction();
    }
}

void SwpProcessEditor::dragMoveEvent(QDragMoveEvent* event)
{
    if (event->mimeData()->hasFormat(ACCEPTED_MIMETYPE))
    {
        event->acceptProposedAction();
    }
}

void SwpProcessEditor::dropEvent(QDropEvent* event)
{
    if (event->mimeData()->hasFormat(ACCEPTED_MIMETYPE))
    {
        const QString data = QString(event->mimeData()->data(
        ACCEPTED_MIMETYPE));

        if (data == START_POINT_MIMETYPE_DATA)
        {
            createNewStartPoint(event->pos());
            event->acceptProposedAction();
        }
        else if (data == END_POINT_MIMETYPE_DATA)
        {
            createNewEndPoint(event->pos());
            event->acceptProposedAction();
        }
        else if (data == DECISION_MIMETYPE_DATA)
        {
            createNewDecision(event->pos());
            event->acceptProposedAction();
        }
        else if (data == ACTIVITY_MIMETYPE_DATA)
        {
            createNewActivity(event->pos());
            event->acceptProposedAction();
        }
        else if (data == JOIN_MIMETYPE_DATA)
        {
            createNewJoin(event->pos());
            event->acceptProposedAction();
        }
        else if (data == SPLIT_MIMETYPE_DATA)
        {
            createNewSplit(event->pos());
            event->acceptProposedAction();
        }
        else if (data == MERGE_MIMETYPE_DATA)
        {
            createNewMerge(event->pos());
            event->acceptProposedAction();
        }
        else
        {
            // do nothing
        }
    }
}

void SwpProcessEditor::createThumbnails()
{
    SwpStartPointGraphicItem startPoint;
    SwpEndPointGraphicItem endPoint;
    SwpDecisionGraphicItem decision;
    SwpActivityGraphicItem activity;
    SwpJoinGraphicItem join;
    SwpSplitGraphicItem split;
    SwpMergeGraphicItem merge;

    SwpActivityConnectorGraphicItem connection(QPointF(0, 0), QPointF(80, 0));

    m_startPointThumbnailDrag = startPoint.thumbnail(DragAndDropRole,
        PREFERRED_THUMBNAIL_SIZE);
    m_startPointThumbnailGallery = startPoint.thumbnail(GalleryRole,
        PREFERRED_GAL_ITEM_ICON_SIZE);

    m_endPointThumbnailDrag = endPoint.thumbnail(DragAndDropRole,
        PREFERRED_THUMBNAIL_SIZE);
    m_endPointThumbnailGallery = endPoint.thumbnail(GalleryRole,
        PREFERRED_GAL_ITEM_ICON_SIZE);

    m_decisionThumbnailDrag = decision.thumbnail(DragAndDropRole,
        PREFERRED_THUMBNAIL_SIZE);
    m_decisionThumbnailGallery = decision.thumbnail(GalleryRole,
        PREFERRED_GAL_ITEM_ICON_SIZE);

    m_activityThumbnailDrag = activity.thumbnail(DragAndDropRole,
        PREFERRED_THUMBNAIL_SIZE);
    m_activityThumbnailGallery = activity.thumbnail(GalleryRole,
        PREFERRED_GAL_ITEM_ICON_SIZE);

    m_joinThumbnailDrag = join.thumbnail(DragAndDropRole,
        PREFERRED_THUMBNAIL_SIZE);
    m_joinThumbnailGallery = join.thumbnail(GalleryRole,
        PREFERRED_GAL_ITEM_ICON_SIZE);

    m_splitThumbnailDrag = split.thumbnail(DragAndDropRole,
        PREFERRED_THUMBNAIL_SIZE);
    m_splitThumbnailGallery = split.thumbnail(GalleryRole,
        PREFERRED_GAL_ITEM_ICON_SIZE);

    m_mergeThumbnailDrag = merge.thumbnail(DragAndDropRole,
        PREFERRED_THUMBNAIL_SIZE);
    m_mergeThumbnailGallery = merge.thumbnail(GalleryRole,
        PREFERRED_GAL_ITEM_ICON_SIZE);

    m_connectorThumbnailGallery = connection.thumbnail(GalleryRole,
        PREFERRED_GAL_ITEM_ICON_SIZE);
    //m_connectorThumbnailConnect = aggregation.connectCursor(
      //  PREFERRED_CURSOR_SIZE);
}

void SwpProcessEditor::createMimeData()
{
    m_startPointMimeData = new QMimeData();
    m_startPointMimeData->setData(ACCEPTED_MIMETYPE,
        START_POINT_MIMETYPE_DATA.toAscii());

    m_endPointMimeData = new QMimeData();
    m_endPointMimeData->setData(ACCEPTED_MIMETYPE,
        END_POINT_MIMETYPE_DATA.toAscii());

    m_decisionMimeData = new QMimeData();
    m_decisionMimeData->setData(ACCEPTED_MIMETYPE,
        DECISION_MIMETYPE_DATA.toAscii());

    m_activityMimeData = new QMimeData();
    m_activityMimeData->setData(ACCEPTED_MIMETYPE,
        ACTIVITY_MIMETYPE_DATA.toAscii());

    m_joinMimeData = new QMimeData();
    m_joinMimeData->setData(ACCEPTED_MIMETYPE,
        JOIN_MIMETYPE_DATA.toAscii());

    m_splitMimeData = new QMimeData();
    m_splitMimeData->setData(ACCEPTED_MIMETYPE,
        SPLIT_MIMETYPE_DATA.toAscii());

    m_mergeMimeData = new QMimeData();
    m_mergeMimeData->setData(ACCEPTED_MIMETYPE,
        MERGE_MIMETYPE_DATA.toAscii());
}

void SwpProcessEditor::createActions()
{
    m_addStartPointAction = new QAction(this);
    m_addEndPointAction = new QAction(this);
    m_addDecisionAction = new QAction(this);
    m_addActivityAction = new QAction(this);
    m_addJoinAction = new QAction(this);
    m_addSplitAction = new QAction(this);
    m_addMergeAction = new QAction(this);

    m_createConnectorAction = new QAction(this);

    m_addPoolLaneAction = new QAction(tr("Add Swimlane"), m_toolbar);
    IconManager* iconManager = IconManager::getInstance();
    if (iconManager->hasIcon("SwpAddSwimlane"))
    {
        m_addPoolLaneAction->setIcon(iconManager->icon("SwpAddSwimlane"));
    }

    m_toolbar->addSeparator();
    m_toolbar->addAction(m_addPoolLaneAction);
}

void SwpProcessEditor::connectSlots()
{
    connect(m_addStartPointAction, SIGNAL(triggered()),
        this, SLOT(createNewStartPoint()));
    connect(m_addEndPointAction, SIGNAL(triggered()),
        this, SLOT(createNewEndPoint()));

    connect(m_addDecisionAction, SIGNAL(triggered()),
        this, SLOT(createNewDecision()));
    connect(m_addActivityAction, SIGNAL(triggered()),
        this, SLOT(createNewActivity()));

    connect(m_addJoinAction, SIGNAL(triggered()),
        this, SLOT(createNewJoin()));
    connect(m_addSplitAction, SIGNAL(triggered()),
        this, SLOT(createNewSplit()));
    connect(m_addMergeAction, SIGNAL(triggered()),
        this, SLOT(createNewMerge()));

    connect(m_createConnectorAction, SIGNAL(triggered()),
        this, SLOT(createConnector()));

    connect(m_addPoolLaneAction, SIGNAL(triggered()),
        this, SLOT(addSwimlane()));
}

qreal SwpProcessEditor::swimlaneAvailablePosition(
    SwpSwimlaneGraphicItem*& previous) const
{
    qreal ret = 0;
    previous = 0;

    GraphicData* data = dynamic_cast<GraphicData* >(m_document->data());

    Q_ASSERT(data != 0); // shouldn't happen

    GDModelQueryMaker queryMaker(*data);

    QList<IGraphicEditorItem*> swimlanes =
        queryMaker.getItems(SWP_SWIMLANE_ITEM);

    foreach (IGraphicEditorItem* item, swimlanes)
    {
        SwpSwimlaneGraphicItem* swimlane =
            dynamic_cast<SwpSwimlaneGraphicItem*>(item);

        Q_ASSERT(swimlane != 0); // item posing as swimlane item

        const qreal candidate = swimlane->scenePos().x() +
            swimlane->boundingRectWithoutHandles().width();

        if (candidate > ret)
        {
            ret = candidate;
            previous = swimlane;
        }
    }

    return ret;
}

// private slots
void SwpProcessEditor::createNewStartPoint(QPoint where)
{
    setModeToSelect();

    QPointF _where;

    if (where.isNull())
    {
        _where = m_view->mapToScene(m_view->viewport()->rect().center());
    }
    else
    {
        _where = m_view->mapToScene(where);
    }

    SwpStartPointGraphicItem* item = new SwpStartPointGraphicItem();
    const QRectF rect = item->boundingRect();
    item->setPos(int(_where.x() - rect.width() / 2),
        int(_where.y() - rect.height() / 2));

    AddGraphicItemCommand* addCommand = new AddGraphicItemCommand(
        m_documentData, item);

    UndoManager::getInstance()->push(m_documentData, addCommand);

    m_view->setFocus();
}

void SwpProcessEditor::createNewEndPoint(QPoint where)
{
    setModeToSelect();

    QPointF _where;

    if (where.isNull())
    {
        _where = m_view->mapToScene(m_view->viewport()->rect().center());
    }
    else
    {
        _where = m_view->mapToScene(where);
    }

    SwpEndPointGraphicItem* item = new SwpEndPointGraphicItem();
    const QRectF rect = item->boundingRect();
    item->setPos(int(_where.x() - rect.width() / 2),
        int(_where.y() - rect.height() / 2));

    AddGraphicItemCommand* addCommand = new AddGraphicItemCommand(
        m_documentData, item);

    UndoManager::getInstance()->push(m_documentData, addCommand);

    m_view->setFocus();
}

void SwpProcessEditor::createNewDecision(QPoint where)
{
    setModeToSelect();

    QPointF _where;

    if (where.isNull())
    {
        _where = m_view->mapToScene(m_view->viewport()->rect().center());
    }
    else
    {
        _where = m_view->mapToScene(where);
    }

    SwpDecisionGraphicItem* item = new SwpDecisionGraphicItem();
    const QRectF rect = item->boundingRect();
    item->setPos(int(_where.x() - rect.width() / 2),
        int(_where.y() - rect.height() / 2));

    AddGraphicItemCommand* addCommand = new AddGraphicItemCommand(
        m_documentData, item);

    UndoManager::getInstance()->push(m_documentData, addCommand);

    m_view->setFocus();
}

void SwpProcessEditor::createNewActivity(QPoint where)
{
    setModeToSelect();

    QPointF _where;

    if (where.isNull())
    {
        _where = m_view->mapToScene(m_view->viewport()->rect().center());
    }
    else
    {
        _where = m_view->mapToScene(where);
    }

    SwpActivityGraphicItem* item = new SwpActivityGraphicItem();
    const QRectF rect = item->boundingRect();
    item->setPos(int(_where.x() - rect.width() / 2),
        int(_where.y() - rect.height() / 2));

    AddGraphicItemCommand* addCommand = new AddGraphicItemCommand(
        m_documentData, item);

    UndoManager::getInstance()->push(m_documentData, addCommand);

    m_view->setFocus();
}

void SwpProcessEditor::createNewJoin(QPoint where)
{
    setModeToSelect();

    QPointF _where;

    if (where.isNull())
    {
        _where = m_view->mapToScene(m_view->viewport()->rect().center());
    }
    else
    {
        _where = m_view->mapToScene(where);
    }

    SwpJoinGraphicItem* item = new SwpJoinGraphicItem();
    const QRectF rect = item->boundingRect();
    item->setPos(int(_where.x() - rect.width() / 2),
        int(_where.y() - rect.height() / 2));

    AddGraphicItemCommand* addCommand = new AddGraphicItemCommand(
        m_documentData, item);

    UndoManager::getInstance()->push(m_documentData, addCommand);

    m_view->setFocus();
}

void SwpProcessEditor::createNewSplit(QPoint where)
{
    setModeToSelect();

    QPointF _where;

    if (where.isNull())
    {
        _where = m_view->mapToScene(m_view->viewport()->rect().center());
    }
    else
    {
        _where = m_view->mapToScene(where);
    }

    SwpSplitGraphicItem* item = new SwpSplitGraphicItem();
    const QRectF rect = item->boundingRect();
    item->setPos(int(_where.x() - rect.width() / 2),
        int(_where.y() - rect.height() / 2));

    AddGraphicItemCommand* addCommand = new AddGraphicItemCommand(
        m_documentData, item);

    UndoManager::getInstance()->push(m_documentData, addCommand);

    m_view->setFocus();
}

void SwpProcessEditor::createNewMerge(QPoint where)
{
    setModeToSelect();

    QPointF _where;

    if (where.isNull())
    {
        _where = m_view->mapToScene(m_view->viewport()->rect().center());
    }
    else
    {
        _where = m_view->mapToScene(where);
    }

    SwpMergeGraphicItem* item = new SwpMergeGraphicItem();
    const QRectF rect = item->boundingRect();
    item->setPos(int(_where.x() - rect.width() / 2),
        int(_where.y() - rect.height() / 2));

    AddGraphicItemCommand* addCommand = new AddGraphicItemCommand(
        m_documentData, item);

    UndoManager::getInstance()->push(m_documentData, addCommand);

    m_view->setFocus();
}

void SwpProcessEditor::createConnector()
{
    setModeToConnect(ConnectionData(CONNECTION_ACTIVITY, 0));
}

void SwpProcessEditor::addSwimlane()
{
    setModeToSelect();

    SwpSwimlaneGraphicItem* previous = 0;
    QPointF where = QPointF(swimlaneAvailablePosition(previous), 0);

    SwpSwimlaneGraphicItem* item = new SwpSwimlaneGraphicItem();
    item->setPos(where);
    item->setPrevious(previous);

    AddSwimlaneCommand* addCommand = new AddSwimlaneCommand(
        m_documentData, item);

    UndoManager::getInstance()->push(m_documentData, addCommand);

    m_view->setFocus();
}
